package similarity;

import java.util.HashMap;
import java.util.Map;

import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.Node;
import org.dom4j.io.SAXReader;

import preprocess.XMLContracter;
import preprocess.XMLDoc;
import utils.Configuration;

public class ElementSimilarity implements Similarity
{
    private static HashMap<XMLDoc, HashMap<XMLDoc, Double>> resultMap;
    private double                                          levelWeight = 2;

    private static void addResult(XMLDoc doc1, XMLDoc doc2, double result)
    {
        if (!Configuration.cacheXML)
            return;
        if (resultMap == null)
            resultMap = new HashMap<XMLDoc, HashMap<XMLDoc, Double>>();

        if (!resultMap.containsKey(doc1))
            resultMap.put(doc1, new HashMap<XMLDoc, Double>());
        resultMap.get(doc1).put(doc2, result);

        if (!resultMap.containsKey(doc2))
            resultMap.put(doc2, new HashMap<XMLDoc, Double>());
        resultMap.get(doc2).put(doc1, result);
    }

    private static double getResult(XMLDoc doc1, XMLDoc doc2)
    {
        if (!Configuration.cacheXML)
            return -1;
        if (resultMap == null)
            return -1;
        if (!resultMap.containsKey(doc1))
            return -1;
        HashMap<XMLDoc, Double> tmp = resultMap.get(doc1);
        if (!tmp.containsKey(doc2))
            return -1;
        return tmp.get(doc2);
    }

    @Override
    public double calcSimilarity(XMLDoc doc1, XMLDoc doc2)
    {
        double result = getResult(doc1, doc2);
        if (result != -1)
            return result;
        HashMap<String, Integer> doc1Info = new HashMap<String, Integer>();
        HashMap<String, Integer> doc2Info = new HashMap<String, Integer>();
        getElementInverseLevelInfo(doc1.getRoot(), doc1, doc1Info);
        getElementInverseLevelInfo(doc2.getRoot(), doc2, doc2Info);
        // System.out.println(doc1Info);
        // System.out.println(doc2Info);
        double sameElementWeight = 0;
        double diffElementWeight = 0;
        for (String name : doc1Info.keySet())
        {
            if (doc2Info.containsKey(name))
            {
                int maxLevel = Math.max(doc1Info.get(name), doc2Info.get(name));
                sameElementWeight += Math.pow(levelWeight, maxLevel);
                doc2Info.remove(name);
            }
            else
            {
                int level = doc1Info.get(name);
                diffElementWeight += Math.pow(levelWeight, level);
            }
        }

        for (int level : doc2Info.values())
        {
            diffElementWeight += Math.pow(levelWeight, level);
        }
        result = sameElementWeight / (sameElementWeight + diffElementWeight);
        addResult(doc1, doc2, result);
        return result;
    }

    private void getElementInverseLevelInfo(Element elem, XMLDoc container,
            Map<String, Integer> map)
    {
        String name = elem.getName();
        if (name != null && !name.isEmpty())
        {
            int inverseLevel = container.getDocHeight()
                    - container.getLevel(elem);
            if (!map.containsKey(name))
                map.put(name, inverseLevel);
            else if (map.get(name) < inverseLevel)
                map.put(name, inverseLevel);
        }
        for (int i = 0; i < elem.nodeCount(); i++)
        {
            Node node = elem.node(i);
            if (node instanceof Element)
                getElementInverseLevelInfo((Element) node, container, map);
        }
    }

    /**
     * @return the levelWeight
     */
    public double getLevelWeight()
    {
        return levelWeight;
    }

    /**
     * @param levelWeight
     *            the levelWeight to set
     */
    public void setLevelWeight(double levelWeight)
    {
        this.levelWeight = levelWeight;
    }

    public static void main(String[] args) throws DocumentException
    {
        SAXReader reader = new SAXReader();
        Document d1 = reader
                .read("file:///D:/INEX Dataset/test set/1474166/44635.xml");
        Document d2 = reader
                .read("file:///D:/INEX Dataset/test set/1480358/7587.xml");
        XMLContracter contracter = new XMLContracter();
        contracter.contract(d1, XMLContracter.TEXT | XMLContracter.ATTRIBUTE);
        contracter.contract(d2, XMLContracter.TEXT | XMLContracter.ATTRIBUTE);
        XMLDoc doc1 = new XMLDoc(d1, "doc1");
        XMLDoc doc2 = new XMLDoc(d2, "doc2");
        System.out.println(new ElementSimilarity().calcSimilarity(doc1, doc2));
    }
}
